#ifndef AVALON_TEMPLATE_HPP
#define AVALON_TEMPLATE_HPP
#include <typeinfo>
#include <boost/mpl/at.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/list.hpp>
#include <boost/mpl/pop_front.hpp>
#include <boost/typeof/typeof.hpp>
#include "Avalon/Base/InstantiationNotSupportedException.hpp"

namespace Avalon {
namespace Detail {
  template<typename TemplateMetaClass, int Args>
  struct TemplateInstantiater {};

  template<typename TemplateMetaClass>
  struct TemplateInstantiater<TemplateMetaClass, 0> {
    typedef BOOST_TYPEOF_TPL((TemplateMetaClass::template Template<
      typename boost::mpl::at_c<
      typename TemplateMetaClass::SupportedTypes, 0>::type>))* FunctionType;

    template<typename MetaClass, typename Signatures>
    struct FindInstantiation {
      static FunctionType Invoke(const std::type_info& type) {
        typedef typename boost::mpl::front<Signatures>::type T;
        if(type == typeid(T)) {
          return MetaClass::template Template<T>;
        }
        return FindInstantiation<MetaClass,
          typename boost::mpl::pop_front<Signatures>::type>::Invoke(type);
      }
    };

    template<typename MetaClass>
    struct FindInstantiation<MetaClass, boost::mpl::l_end> {
      static FunctionType Invoke(const std::type_info& type) {
        BOOST_THROW_EXCEPTION(InstantiationNotSupportedException());
      }
    };

    static FunctionType Instantiate(const std::type_info& type) {
      return FindInstantiation<TemplateMetaClass,
        typename TemplateMetaClass::SupportedTypes>::Invoke(type);
    }
  };

  template<typename TemplateMetaClass>
  struct TemplateInstantiater<TemplateMetaClass, 1> {
    typedef BOOST_TYPEOF_TPL((TemplateMetaClass::template Template<
      typename boost::mpl::at_c<typename boost::mpl::front<
      typename TemplateMetaClass::SupportedTypes>::type, 0>::type,
      typename boost::mpl::at_c<typename boost::mpl::front<
      typename TemplateMetaClass::SupportedTypes>::type, 1>::type>))*
      FunctionType;

    template<typename MetaClass, typename Signatures>
    struct FindInstantiation {
      static FunctionType Invoke(const std::type_info& type0,
          const std::type_info& type1) {
        typedef typename boost::mpl::front<Signatures>::type Signature;
        typedef typename boost::mpl::at_c<Signature, 0>::type T0;
        typedef typename boost::mpl::at_c<Signature, 1>::type T1;
        if(type0 == typeid(T0) && type1 == typeid(T1)) {
          return MetaClass::template Template<T0, T1>;
        }
        return FindInstantiation<MetaClass,
          typename boost::mpl::pop_front<Signatures>::type>::Invoke(type0,
          type1);
      }
    };

    template<typename MetaClass>
    struct FindInstantiation<MetaClass, boost::mpl::l_end> {
      static FunctionType Invoke(const std::type_info& type0,
          const std::type_info& type1) {
        BOOST_THROW_EXCEPTION(InstantiationNotSupportedException());
      }
    };

    static FunctionType Instantiate(const std::type_info& type0,
        const std::type_info& type1) {
      return FindInstantiation<TemplateMetaClass,
        typename TemplateMetaClass::SupportedTypes>::Invoke(type0, type1);
    }
  };

  template<typename TemplateMetaClass>
  struct TemplateInstantiater<TemplateMetaClass, 2> {
    typedef BOOST_TYPEOF_TPL((TemplateMetaClass::template Template<
      typename boost::mpl::at_c<typename boost::mpl::front<
      typename TemplateMetaClass::SupportedTypes>::type, 0>::type,
      typename boost::mpl::at_c<typename boost::mpl::front<
      typename TemplateMetaClass::SupportedTypes>::type, 1>::type,
      typename boost::mpl::at_c<typename boost::mpl::front<
      typename TemplateMetaClass::SupportedTypes>::type, 2>::type>))*
      FunctionType;

    template<typename MetaClass, typename Signatures>
    struct FindInstantiation {
      static FunctionType Invoke(const std::type_info& type0,
          const std::type_info& type1, const std::type_info& type2) {
        typedef typename boost::mpl::front<Signatures>::type Signature;
        typedef typename boost::mpl::at_c<Signature, 0>::type T0;
        typedef typename boost::mpl::at_c<Signature, 1>::type T1;
        typedef typename boost::mpl::at_c<Signature, 2>::type T2;
        if(type0 == typeid(T0) && type1 == typeid(T1) && type2 == typeid(T2)) {
          return MetaClass::template Template<T0, T1, T2>;
        }
        return FindInstantiation<MetaClass,
          typename boost::mpl::pop_front<Signatures>::type>::Invoke(type0,
          type1, type2);
      }
    };

    template<typename MetaClass>
    struct FindInstantiation<MetaClass, boost::mpl::l_end> {
      static FunctionType Invoke(const std::type_info& type0,
          const std::type_info& type1, const std::type_info& type2) {
        BOOST_THROW_EXCEPTION(InstantiationNotSupportedException());
      }
    };

    static FunctionType Instantiate(const std::type_info& type0,
        const std::type_info& type1, const std::type_info& type2) {
      return FindInstantiation<TemplateMetaClass,
        typename TemplateMetaClass::SupportedTypes>::Invoke(type0, type1,
        type2);
    }
  };
}

  //! Returns a function template's instantiation.
  /*!
    \param type The template parameter.
    \return The function template instantiated with the specified types.
  */
  template<typename TemplateMetaClass>
  typename Detail::TemplateInstantiater<TemplateMetaClass, 0>::FunctionType
      Instantiate(const std::type_info& type) {
    return Detail::TemplateInstantiater<TemplateMetaClass, 0>::Instantiate(
      type);
  }

  //! Returns a function template's instantiation.
  /*!
    \param type0 The first template parameter.
    \param type1 The second template parameter.
    \return The function template instantiated with the specified types.
  */
  template<typename TemplateMetaClass>
  typename Detail::TemplateInstantiater<TemplateMetaClass, 1>::FunctionType
      Instantiate(const std::type_info& type0, const std::type_info& type1) {
    return Detail::TemplateInstantiater<TemplateMetaClass, 1>::Instantiate(
      type0, type1);
  }

  //! Returns a function template's instantiation.
  /*!
    \param type0 The first template parameter.
    \param type1 The second template parameter.
    \param type2 The third template parameter.
    \return The function template instantiated with the specified types.
  */
  template<typename TemplateMetaClass>
  typename Detail::TemplateInstantiater<TemplateMetaClass, 2>::FunctionType
      Instantiate(const std::type_info& type0, const std::type_info& type1,
      const std::type_info& type2) {
    return Detail::TemplateInstantiater<TemplateMetaClass, 2>::Instantiate(
      type0, type1, type2);
  }
}

#endif // AVALON_TEMPLATE_HPP
